#include <iostream>
#include <boost/lexical_cast.hpp>
#include <boost/foreach.hpp>
#include "MySQLConnector.h"
#include "MySQLConnectorError.h"
#include "../config/Config.h"

#define foreach BOOST_FOREACH

using namespace kc_sf;

// --------------------------------------------------------- //

MySQLConnector::MySQLConnector()
{
	// inicjalizacja handlera do polaczenia z baza
	m_connection = mysql_init (NULL);
	if( m_connection == NULL )
		throw MySQLConnectorError( "Błąd inicjalizacji obiektu do połączenia z bazą danych. Errno: " + readMySQLError() );

	Config &cfg = Config::getInstance();

	mysql_options( m_connection, MYSQL_SET_CHARSET_NAME, "utf8" );
	mysql_options( m_connection,MYSQL_INIT_COMMAND, "SET NAMES utf8" );

	// ustanowienie polaczenia
	if( mysql_real_connect(
		m_connection,
		"localhost",
		cfg.getDatabaseRootName().c_str(),
		cfg.getDatabaseRootPassword().c_str(),
		cfg.GetDatabaseName().c_str(),
		0, NULL, 0 ) == NULL )
	{
		mysql_errno( m_connection );
		mysql_error( m_connection );

		throw MySQLConnectorError( "Błąd ustanowienia połączenia z bazą danych. Errno: " + readMySQLError() );
	}
}

// --------------------------------------------------------- //

MySQLConnector::~MySQLConnector()
{
	if( m_connection )
		mysql_close( m_connection );
}

// --------------------------------------------------------- //

DatabaseRecord MySQLConnector::readTableColumnNames( const std::string &table_name ) const
{
	if( !m_connection )
		throw MySQLConnectorError( "Przed rozpoczęciem pobierania danych trzeba się połączyć z bazą danych (connect)" );

	if( mysql_query( m_connection, ( "SELECT * from " + table_name ).c_str() ) )
		throw MySQLConnectorError( "Błąd odczytywania nazw kolumn z tabeli " + table_name + "\nErrno " + readMySQLError() );

	MYSQL_RES *result = mysql_store_result( m_connection );
	std::vector <std::string> column_names;
	while( MYSQL_FIELD *field = mysql_fetch_field( result ) )
	{
		column_names.push_back( field->name );
	}

	return column_names;
}

// --------------------------------------------------------- //

DatabaseTable MySQLConnector::readTable( const std::string &table_name ) const
{
	return query( "SELECT * from " + table_name );
}

// --------------------------------------------------------- //

DatabaseTable MySQLConnector::query( const std::string &query_str ) const
{
	if( !m_connection )
		throw MySQLConnectorError( "Przed rozpoczęciem pobierania danych trzeba się połączyć z bazą danych (connect)" );

	if( mysql_query( m_connection, query_str.c_str() ) )
		throw MySQLConnectorError( "Błąd pobierania danych z bazy danych\nErrno " + readMySQLError() );

	MYSQL_RES *result = mysql_store_result( m_connection );
	int num_fields = mysql_num_fields( result );

	std::vector <std::string> column_names;
	while( MYSQL_FIELD *field = mysql_fetch_field( result ) )
	{
		column_names.push_back( field->name );
	}

	DatabaseTable table( column_names );

	MYSQL_ROW row;
	while ( ( row = mysql_fetch_row( result ) ) )
	{
		DatabaseRecord record;
		for( int i = 0; i < num_fields; i++ )
		{
			record.push_back( row[i] ? row[i] : "NULL" );
		}

		table.insert( record );
	}

	mysql_free_result( result );

	return table;
}

// --------------------------------------------------------- //

void MySQLConnector::queryWithoutResult( const std::string &query_str ) const
{
	if( !m_connection )
		throw MySQLConnectorError( "Przed rozpoczęciem pobierania danych trzeba się połączyć z bazą danych (connect)" );

	std::cout << "FIX QUERY: " << query_str << std::endl;
	if( mysql_query( m_connection, query_str.c_str() ) )
		throw MySQLConnectorError( "Błąd pobierania danych z bazy danych\nErrno " + readMySQLError() );
}

// --------------------------------------------------------- //

size_t MySQLConnector::insertInto(
	const std::string &table_name,
	const DatabaseRecord &column_names,
	const DatabaseRecord &values ) const
{
	if( column_names.size() == 0 || values.size() == 0 )
		throw MySQLConnectorError( "Nie podano kolumn do wprowadzenia danych do bazy (insert)" );

	std::string query = "INSERT INTO " + table_name + "(";
	foreach( std::string column_name, column_names )
	{
		query += column_name + ", ";
	}
	query.erase( query.size() - 2, 2 ); // usun ostatni ', '
	query += ") VALUES(";
	foreach( std::string value, values )
	{
		query += "'" + value + "', ";
	}
	query.erase( query.size() - 2, 2 ); // usun ostatni ', '
	query += ")";

	if( mysql_query( m_connection, query.c_str() ) )
		throw MySQLConnectorError( "Błąd wprowadzania danych do tabeli " + table_name + "\nErrno " + readMySQLError() );

	return mysql_insert_id( m_connection );
}

// --------------------------------------------------------- //

std::string MySQLConnector::readMySQLError() const
{
	std::string err = "" + mysql_errno( m_connection ) + ' '
		+ std::string( mysql_error( m_connection ) );

	return err;
}
